//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using C = config;

namespace DataLayerBuilder
{
    public partial class DataGen
    {
        interface ISqlItem
        {
        }

        interface ISqlCreate
        {
            string SQLCreate { get; }
        }

        interface ISqlRelation
        {
            string SQLRelation { get; }
        }

        class SystemDef
        {
            private C.DataLayout src;

            public SystemDef(C.DataLayout src)
            {
                this.src = src;
            }

            public string Name { get { return src.name; } }
            public string Namespace { get { return src.@namespace; } }
            public string DependsOnLayout { get { return src.dependsOnLayout; } }

            public List<string> ImportTypes
            {
                get
                {
                    if (src.Imports == null)
                        return new List<string>();

                    return new List<string>(src.Imports);
                }
            }
        }

        public class ImplDef
        {
            C.Implements src;

            public ImplDef(C.Implements src)
            {
                this.src = src;
            }

            public string Interface
            {
                get
                {
                    switch (src.@interface)
                    {
                        case config.@interface.IBlob:
                            return "IBlob";
                        case config.@interface.ISecurityPrincipal:
                            return "ISecurityPrincipal";
                        case config.@interface.ISecurityCredential:
                            return "ISecurityCredential";
                        case config.@interface.ISecurityCredentialCollection:
                            return "ISecurityCredentialCollection";          
                        default:
                            throw new NotImplementedException("Unknown type " + src.@interface);
                    }
                }
            }
        }

        
        /// <summary>
        /// Object representing the Interface implemented by an object collection.
        /// </summary>
        public class CollImplDef
        {
            C.CollImplements src;

            public CollImplDef(C.CollImplements src)
            {
                this.src = src;
            }

            public string Interface
            {
                get
                {
                    switch (src.@interface)
                    {
                        case config.@interface.ISecurityCredentialCollection:
                            return "ISecurityCredentialCollection";
                        default:
                            throw new NotImplementedException("Unknown type " + src.@interface);
                    }
                }
            }
        }

        /// <summary>
        /// Object representing the global settings.
        /// </summary>
        class GlobalSettingsDef
        {
            // List of setting objects.
            public List<ConfigItemDef> settings = new List<ConfigItemDef>();
        }

        /// <summary>
        /// Object representing the System policies.
        /// </summary>
        class SystemPolicyDef
        {
            // List of policy objects.
            public List<ConfigItemDef> policys = new List<ConfigItemDef>();
            // List of role objects
            public List<RoleDef> roles = new List<RoleDef>();
            // List of builtin roles.
            public List<string> builtinRoles = new List<string>();            
        }

        /// <summary>
        /// Object representing a system role.
        /// </summary>
        class RoleDef
        {
            private C.Role src;           

            public RoleDef(C.Role src)
            {
                this.src = src;                
            }

            public string Name { get { return src.name; } }
            public config.roletype Type { get { return src.type; } }
            public string UILocResource { get { return src.uiLocResource; } }
            public string UINolocResourceText { get { return src.uiNolocResourceText; } }
        }

        /// <summary>
        /// Object representing a configuration item.
        /// </summary>
        class ConfigItemDef 
        {
            private string name;
            private config.datatype type;
            private string uiLocResource;
            private string uiNolocResourceText;

            public ConfigItemDef(string name, config.datatype type, string uiLocResource, string uiNolocResourceText)                
            {
                this.name = name;
                this.type = type;
                this.uiLocResource = uiLocResource;
                this.uiNolocResourceText = uiNolocResourceText;
            }

            public string Name { get { return this.name; } }
            public config.datatype Type { get { return this.type; } }
            public string UILocResource { get { return this.uiLocResource; } }
            public string UINolocResourceText { get { return this.uiNolocResourceText; } }
        }
       
        class ObjectDef
        {
            public bool AutoGenerated = false;
            public bool IsBlobStore = false;

            private C.Object src;

            public ObjectDef(C.Object src)
            {
                this.src = src;
            }
            public C.visibility Visibility { get { return src.visibilitySpecified ? src.visibility : config.visibility.@public; } }

            public ObjectDef(C.Object src, SecPrivilegeDef map)
            {
                this.src = src;
                this.SecPrivDef = map;
            }

            public C.Securable Securable { get { return src.Securable; } }
            public bool Versioned { get { return src.versioned; } }
            public bool Bindable { get { return src.bindable; } }
            public string Name { get { return src.name; } }
            public config.objectParent Parent { get { return src.parent; } }
            public string BaseClass { get { return src.parent.ToString(); } }
            public List<FieldDef> Fields = new List<FieldDef>();
            public List<EnumDef> Enums = new List<EnumDef>();
            public List<RefDef> References = new List<RefDef>();
            public List<RefDef> RevRef = new List<RefDef>();
            public List<SecPrincipalRefDef> SecPrinRef = new List<SecPrincipalRefDef>();
            public List<RelationDef> Relations = new List<RelationDef>();
            public List<ImplDef> Implements = new List<ImplDef>();
            public List<CollImplDef> CollImplements = new List<CollImplDef>();
            public SecPrivilegeDef SecPrivDef;

            public List<string> SearchFields
            {
                get
                {
                    List<string> results = new List<string>();

                    results.Add("ID");

                    if (this.BaseClass == ParentClasses.NamedObject.ToString())
                        results.Add("Name");

                    foreach (FieldDef field in Fields)
                    {
                        if (field.SQLSearchClause != null)
                            results.Add(field.Name);
                    }

                    foreach (EnumDef myEnum in Enums)
                    {
                        if (myEnum.SQLSearchClause != null)
                            results.Add(myEnum.Name);
                    }

                    return results;
                }
            }

            public override string ToString()
            {
                return string.Format("Object [{0}]", this.Name);
            }
        }

        class EnumDef : FieldDef
        {
            public string EnumType;
            public List<string> Values = new List<string>();

            public EnumDef(string name, string type, string storeAs, bool required)
                : base(name, C.datatype.@string, storeAs, required)
            {
                EnumType = type;
            }
        }

        internal class FieldDef : ISqlItem, ISqlCreate
        {
            private string name;
            public config.datatype Type;
            private string storeAs;
            private bool required;

            public FieldDef(string name, config.datatype type, string storeAs, bool required)
            {
                this.name = name;
                this.Type = type;
                this.storeAs = storeAs;
                this.required = required;
            }

            public string Name
            {
                get
                {
                    return name;
                }
            }

            public bool Required
            {
                get
                {
                    return required;
                }
            }

            public bool IsBLOB
            {
                get
                {
                    switch (this.Type)
                    {
                        case config.datatype.image:
                        case config.datatype.byteArray:
                            return true;
                        default:
                            return false;
                    }
                }
            }

            public string SQLName
            {
                get
                {
                    return storeAs ?? name;
                }
            }

            public string SQLCreate
            {
                get
                {
                    return string.Format("	[{0}] {1} NULL,", this.SQLName, SQLType);
                }
            }

            public string SQLSPType
            {
                get
                {
                    return string.Format("@{0} {1}{2},", this.SQLName, SQLType,
                        Required ? "" : " = NULL");
                }
            }

            public string SQLSearchClause
            {
                get
                {
                    if (this.Type == config.datatype.@string ||
                        this.Type == config.datatype.text ||
                        this.Type == config.datatype.uri)
                    {
                        return string.Format("                    [{0}] LIKE ('%' + @Text + '%') OR", this.SQLName);
                    }

                    return null;
                }
            }

            private string SQLType
            {
                get
                {
                    if (IsBLOB)
                    {
                        /* No action for this item. Handled via external blob entities */
                        throw new FieldShouldBeSkipped();
                    }

                    switch (this.Type)
                    {
                        case config.datatype.uri:
                        case config.datatype.@string:
                            return "[nvarchar](MAX)";
                        case config.datatype.integer:
                        case config.datatype.color:
                            return "[int]";
                        case config.datatype.text:
                            return "[ntext]";
                        case config.datatype.guid:
                            return "[uniqueidentifier]";
                        case config.datatype.boolean:
                            return "[bit]";
                        case config.datatype.dateTime:
                            return "[datetime]";
                        default:
                            throw new ApplicationException(string.Format("Type unknown: {0}", this.Type));
                    }
                }
            }

            public override string ToString()
            {
                return string.Format("Enum [{0}]", this.Name);
            }
        }

        /// <summary>
        /// Represents a security principal.
        /// </summary>
        class SecPrincipalRefDef
        {
            private ObjectDef host;
            private C.SecurityPrincipalRef src;
            public SecPrincipalRefDef(ObjectDef host, C.SecurityPrincipalRef src)
            {
                this.host = host;
                this.src = src;
            }

            public string Name { get { return this.src.name; } }
            public string Type { get { return "ISecurityPrincipal"; } }
            public bool Required { get { return src.required; } }
            public C.visibility Visibility { get { return src.visibilitySpecified ? src.visibility : config.visibility.@public; } }
            public C.visibility RevVisibility { get { return src.revVisibilitySpecified ? src.revVisibility : config.visibility.@public; } }                       
            public bool IsTwoWay { get { return src.twoWay; } }            
        }

        /// <summary>
        /// Represents a security privilege definition.
        /// </summary>
        class SecPrivilegeDef
        {
            private ObjectDef host;            
            private Dictionary<string,List<string>> privPermMap;

            public SecPrivilegeDef(ObjectDef host, Dictionary<string, List<string>> privPermDict)
            {
                this.host = host;                
                this.privPermMap = privPermDict;
            }

            public string Name { get { return host.Name; } }            
            public string Type { get { return "IPrivilege"; } }
            public Dictionary<string, List<string>> PrivPermMapping { get { return this.privPermMap; } }
        }

        class RefDef : ISqlItem, ISqlCreate, ISqlRelation
        {
            private ObjectDef host;
            private FieldDef field;
            private C.Reference src;

            static int checkCount = 0;

            public RefDef(ObjectDef host, C.Reference src)
            {
                this.host = host;
                this.src = src;
                field = new FieldDef(string.Format("{0}ID", Name), config.datatype.guid,
                    StoreAs == null ? null : string.Format("{0}ID", StoreAs), false);
            }

            public string Name
            {
                get
                {
                    return this.src.name;
                }
            }

            public string Type
            {
                get
                {
                    return src.@object;
                }
            }
            public C.visibility Visibility { get { return src.visibilitySpecified ? src.visibility : config.visibility.@public; } }
            public C.visibility RevVisibility { get { return src.revVisibilitySpecified ? src.revVisibility : config.visibility.@public; } }

            public bool Required { get { return src.required; } }
            public string StoreAs { get { return src.storeAs; } }

            public bool IsTwoWay { get { return src.twoWay; } }
            public ObjectDef Source { get { return host; } }
            public string RevName { get { return src.revName; } }

            public string SQLCreate
            {
                get
                {
                    return field.SQLCreate;
                }
            }

            public string SQLName
            {
                get
                {
                    return field.SQLName;
                }
            }

            public string SQLSPType
            {
                get
                {
                    return field.SQLSPType;
                }
            }

            public string SQLRelation
            {
                get
                {
                    StringBuilder enforceSQL = new StringBuilder(
                        "ALTER TABLE [dbo].[%OBJECT%s]  WITH CHECK ADD  CONSTRAINT [FK_%OBJECT%_%REF_OBJ%_%COUNT%] FOREIGN KEY([%NAME%])\r\n" +
                        "	REFERENCES [dbo].[%REF_OBJ%s] ([ID])\r\nGO\r\n");

                    // Update statement
                    enforceSQL.Replace("%NAME%", field.SQLName);
                    enforceSQL.Replace("%REF_OBJ%", Type);
                    enforceSQL.Replace("%COUNT%", (++checkCount).ToString());

                    return enforceSQL.ToString();
                }
            }

            public override string ToString()
            {
                return string.Format("Reference [{0}]", this.Name);
            }
        }

        class RelationDef
        {
            private C.Relation src;

            public RelationDef(C.Relation src)
            {
                this.src = src;
            }

            public string One { get { return src.one; } }
            public string OneName { get { return src.oneName; } }
            public string Two { get { return src.two; } }
            public string TwoName { get { return src.twoName; } }
            public string Object { get { return src.@object; } }
            public List<FieldDef> Fields = new List<FieldDef>();
            public List<EnumDef> Enums = new List<EnumDef>();
            public bool IsTwoWay { get { return src.twoWay; } }
            public C.visibility Visibility { get { return src.visibilitySpecified ? src.visibility : config.visibility.@public; } }
            public bool IsSelfRelation { get { return src.one == src.two; } }

            public string Other(string test)
            {
                string other;
                if (this.One == test)
                {
                    other = this.Object == null ? this.Two : this.Object;
                }
                else
                {
                    other = this.Object == null ? this.One : this.Object;
                }
                return other;
            }

            public string ObjectClassName(string other)
            {
                string objName = null;

                if (!string.IsNullOrEmpty(this.Object))
                {
                    // Relationship with fields
                    objName = string.Format("{0}", this.Object);
                }
                else
                {
                    objName = Other(other);
                }

                return objName;
            }

            public string OtherName(string other)
            {
                string objName = other;

                if (objName == this.One && this.TwoName != null)
                    objName = this.TwoName;
                else if (objName == this.Two && this.OneName != null)
                    objName = this.OneName;

                return objName;
            }

            public string OtherName2(string other)
            {
                string objName = other;

                if (objName == this.One && this.OneName != null)
                    objName = this.OneName;
                else if (objName == this.Two && this.TwoName != null)
                    objName = this.TwoName;
                else
                    objName = Other(objName);

                return objName;
            }

            public string OtherObjectName(string objectName)
            {
                if (objectName == this.One)
                {
                    return this.Two;
                }
                else
                {
                    return this.One;
                }
            }

            public string OneSearchName()
            {
                return this.OneName ?? this.One;
            }
            public string TwoSearchName()
            {
                return this.TwoName ?? this.Two;
            }

            public string OtherSearchName(string objectName)
            {
                if (objectName == this.One)
                {
                    return this.TwoName ?? this.Two;
                }
                else
                {
                    return this.OneName ?? this.One;
                }
            }

            public enum RelationDirection
            {
                One,
                Two,
            }

            /// <summary>
            /// Returns the name of the other field to be used for relation purposes
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public string OtherFieldName(RelationDirection me)
            {
                switch (me)
                {
                    case RelationDirection.One:
                        // Are = _2, Need = _1
                        return this.OneName ?? string.Format("{0}_1", this.One);
                    case RelationDirection.Two:
                        // Are = _1, Need = _2
                        return this.TwoName ?? string.Format("{0}_2", this.Two);
                    default:
                        throw new NotImplementedException();
                }
            }

            /// <summary>
            /// Returns the name of the other field to be used for relation purposes
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public string OtherKeyName(RelationDirection me)
            {
                switch (me)
                {
                    case RelationDirection.One:
                        // Are = _2, Need = _1
                        return string.Format("{0}_1", this.One);
                    case RelationDirection.Two:
                        // Are = _1, Need = _2
                        return string.Format("{0}_2", this.Two);
                    default:
                        throw new NotImplementedException();
                }
            }

            public string OtherSelfName2(string other)
            {
                string objName = this.OneName;

                if (this.OneName != null && this.OneName == other)
                    objName = this.TwoName;

                return objName;
            }

            public string TableName
            {
                get
                {
                    string objTableName;

                    if (!string.IsNullOrEmpty(this.Object))
                    {
                        // Relationship with fields
                        objTableName = string.Format("{0}{1}s", this.One, this.Object);
                    }
                    else
                    {
                        objTableName = string.Format("{0}{1}s",
                            this.OtherName2(this.Two), this.OtherName2(this.One));
                    }

                    return objTableName;
                }
            }

            public override string ToString()
            {
                return string.Format("Relation: [{0}] [{1}] [{2}]{3}",
                    this.Object, this.One, this.Two, this.IsTwoWay ? " (TW)" : "");
            }
        }
    }
}
